Repository navigation
Conversation
Watch plugins that publish a Bluetooth service need the bluetooth-peripheral background mode. It is on by default; uncommenting INFOPLIST_PREPROCESSOR_DEFINITIONS in LoopConfigOverride.xcconfig leaves it out. Loop points at the fork until its PR is merged.
LoopKit's SmartwatchManager plugin type and Loop's host, with the LupedSmartwatchKit submodule, its projects in the workspace and its plugin bundle in the LoopWorkspace scheme. elbbepTransport (Swift package, tagged releases) is the Bluetooth link to the Pebble. LoopKit and Loop point at the forks until their PRs are merged.
Bolus, carbs, alert mute and presets requested from the watch, each refused unless the plugin declares it and the user allows it in Watch Permissions. The Pebble plugin declares none yet. Revert this commit (and the matching commit in LoopKit, Loop and LupedSmartwatchKit) to leave remote actions out.
The base strings (47e7c4ab) and the remote action strings (647c0dd4), so they reach translation. To leave remote actions out, revert 647c0dd4 together with e3425e1d in Loop.
…async Like CGMManager and PumpManager since LoopKit #685: the plugin's delete() stops its work and awaits the host's removal, replacing delete(completion:) and notifyDelegateOfDeletion.
4243102 to
f6ac0b9
Compare
|
Force-pushed
|
|
Adding some more remote control options at the moment, marking it as draft because of this |
…endations, carb absorption (elbbepTransport 1.1.3) - LupedSmartwatchKit serves Luped Remote, a Pebble watchapp with remote actions (off until turned on in the plugin; the store link is in its settings), and declares the remote actions it sends. The host's Watch Permissions, all off by default, still decide each one. - LoopKit and Loop: a watch can ask for Loop's bolus recommendation for carbs it is about to add, and carbs from the watch may say how fast they are absorbed. - LoopKit's watch limits: 5 minutes between watch boluses and between carb entries, and watch boluses in 24 hours up to 3 maximum boluses. - elbbepTransport 1.1.3.
…sets and End mute for Luped Remote Luped Remote asks for the host's presets and can start or end them, and can end a mute of alerts (a request of 0 minutes). Loop's watch permissions are under Third-Party Smartwatch headers.
… and its other bumps
Without a declared remote capability the plugin hides Luped Remote's settings and never opens its watch session.
…change in behaviour)
… change in behaviour)
|
Turning remote actions off without reverting commits This series now includes a set of remote capabiltities, i.e. remote bolusing, carbs, mute, temptargets(trio only) , overrides, which also requires installation of another watchside app "luped remote". If you want I can easily disable any remote capabilities for now by changing one constant in the plugin (LupedSmartwatchKit/LupedSmartwatchManager.swift): - public static let remoteCapabilities: SmartwatchCapabilities = [.remoteMute, .remoteCarbs, .remoteBolus, .remoteOverride, .remoteTempTarget]
+ public static let remoteCapabilities: SmartwatchCapabilities = []The plugin then neither declares nor requests a remote capability, so effectiveCapabilities has none. As a result:
The LoopKit and Loop remote-action code then stays in place, but is effectively unused. |
|
For completeness sake, here is the screenshots of the "Luped Remote" watch side app, that can be optjonally enabled inside the Luped Settings inside Loop. The split is a technical necessity since a normal Pebble watchface cannot do user interaction. This means any remote action such as bolusing, carbs etc needs its own watchside app. Loop will connect to both "Luped CGM" and "Luped Remote" as necessary Main screen and Mute alerts: Loop's own mute times, and End mute
Add carbs: amount, absorption, confirmation
Presets: Loop's override presets, asked for when opened; the running one is marked, long names are shortened
Bolus: off until turned on in the watch's own settings (Loop's Watch Permissions must allow it too)
A bolus starts with its carbs (0 for none). The watch then asks Loop for its recommendation for those carbs and starts the picker there. Loop refuses a bolus over its recommendation.
When Loop refuses (for example a bolus over its recommendation, or no recent glucose), the last screen says "Not done" with Loop's reason. |
…t restarting the watchface The plugin asks the watchface whether it has a speaker until it has reported one, so a watch added again while its watchface kept running offers Alarm Sound right away.
…aiting reading while Loop is in the background The plugin holds an iOS background task while a reading waits for the loop result, so iOS doesn't suspend Loop before it has gone to the watch.
|
went back to draft on 5 Nov while I added remote options; these a are done:
On a Pebble Time 2, checked on the phone: presets started and ended from the watch, a 30-minute mute and End mute from the watch, a bolus with carbs from the watch, and Alarm Sound appearing after the watch was added again. The description and screenshots are also updated. Ready for review @ps2 |















LoopWorkspace: watch plugins and the Pebble plugin
Adds the LupedSmartwatchKit Pebble plugin and points LoopKit and Loop at the forks until their PRs (LoopKit, Loop) are merged.
Commits
Up to date with
next-dev(3aeff3b, #571), merged in19d7098. Loop isnext-dev'sf161c29e(with the G7 fix, #2652) plus this PR's Loop commits (Loop #2646, which merges Loopnext-devin917e0d27).115e7b3bluetooth-peripheralbackground mode, with a commented-out switch inLoopConfigOverride.xcconfigd4f1e5dLupedSmartwatchKitsubmodule, its projects in the workspace and its plugin bundle in theLoopWorkspacescheme; elbbepTransport 0.3.59 (Swift package, tagged releases), the Bluetooth link to the Pebble7e97b08LupedSmartwatchManager.remoteCapabilitiesin the plugin (possible since8f74dbe; named so in20ada7f). The plugin then neither declares nor requests them, Loop hides Watch Permissions, and Luped Remote's settings and watch session are gone.d70a554265952cc, and7c2999c5for remote actions), so they reach translation2210106f6ac0b9SmartwatchManager.delete()is async, likeCGMManagerandPumpManagersince #570 (LoopKit #685)ea74742e3bcb7bca58869f7156d128928727). elbbepTransport 1.3.1. No LoopKit change19d7098next-dev(#571: Loop, LibreTransmitter, NightscoutService and dexcom-share-client-swift); Loop points at917e0d27, Loop #2646 with Loopnext-devmerged in332d9fa16eabfc0); "3 maximum boluses a day" read as three boluses8f74dbe20ada7fremoteCapabilities(wasremoteActions), in LoopKit's capability terms. No change in behavioura2332f8remoteCapabilitiesisn't empty; the plugin no longer keeps its own copy of LoopKit's remote capabilities. No change in behaviourece3842COMMAND speaker, once per session), so Alarm Sound shows without restarting the watchface, for example after the watch was added again. No LoopKit or Loop changec6edfaca9e93df818c02f8)Add a SmartwatchManager plugin type, with a Pebble plugin
Watches can now ship as plugins, like CGMs and pumps, instead of as code inside Loop. They appear under Settings → Devices → Add Watch and write to the device log. The first plugin, LupedSmartwatchKit, shows Loop's glucose, loop status and glucose alerts on a Pebble running the Luped CGM watchface.
Supported watches: Core Devices' Pebble Time 2 and Pebble Round 2. The watchface is built for those two platforms only (
emery,gabbro). Older Pebbles, Apple Watch and Garmin aren't part of this PR; Apple Watch keeps Loop's existing app. Daily use is on a Time 2.Using it: for people with diabetes and caregivers
This section is for the person wearing the watch, or a family member. It explains what the watch does; it isn't medical advice. Talk to your care team about your alert settings.
What it does: your Pebble shows the glucose Loop sees, and buzzes when Loop raises a glucose alert. It's an extra screen on your wrist: your iPhone still alarms as before. The watchface itself can't give insulin or enter carbs; the optional Luped Remote app below can, only if you turn it on and allow each action in Loop.
What you need:
Setting it up: in Loop, go to Settings → Devices → Add Watch → Pebble and follow the steps. Add Watch is for watches other than Apple Watch: Loop's Apple Watch app needs no setup there, and is installed from the Watch app on your iPhone. Loop shows when it is waiting for the watch, when the watch is connected, and when the watch has received your first reading. If the watch is added in another diabetes app, remove it there first: the watch follows one app at a time.
On the watch:
When something is wrong:
Always check Loop on your iPhone, or your CGM, before acting on what the watch shows, especially when it shows ERR or the Bluetooth symbol.
Settings (Settings → Devices → Pebble): Layout, Status Line and Last Loop; Watch Alarms, which turns the buzz on or off (the ring still changes colour); Alarm Sound on a Pebble Time 2, off unless you choose Urgent Low Only or All Glucose Alarms: the watch then beeps as well as buzzes, at its own speaker volume, and Quiet Time on the watch may silence the beep; Send Now; Find My Pebble, which makes the watch buzz, beep (Pebble Time 2, at the watch's speaker volume) and light up for a minute, until you press any button on it; Diagnostics, with the watch's battery and a message log for troubleshooting; and Delete Pebble.
Luped Remote (optional): a separate watch app for asking Loop to do things from the watch's buttons: mute alerts (or end the mute), add carbs, start or end a preset, and a bolus.
Check Loop on your iPhone before and after asking for a bolus from the watch. This isn't medical advice: decide with your care team whether to allow boluses from a watch.
Screenshots
From an iPhone with a Pebble Time 2 running Luped CGM, on this PR's branches; the Add Watch picker is from my personal build (see below), and the watch photos, the Large Glucose layout and Diagnostics are from earlier builds, unchanged since. The phone is set to Norwegian, so Loop's existing labels are Norwegian; the new texts have no translations yet. Luped Remote on the watch: screenshots in this comment.
The watch
Adding a watch
Next, not in this PR: a second plugin, GarminSmartwatchKit, built on the same plugin type from Trio's GarminManager (display only). My personal build lists it as coming soon and doesn't load it; the Add Watch screenshot above is from that build.
Third-party smartwatch
Pebble settings
Diagnostics
Branches
All are named
smartwatch-manager-reviewand sit on what LoopWorkspacenext-devpins (Loop onf161c29eplus its commits, see Loop #2646). The table sorts each repo's commits into the plugin type (display and alerts) and remote actions (requests from the watch, off by default); in each branch's history the two are interleaved. Declaring no remote capabilities in the plugin (an emptyremoteCapabilities) turns the remote actions off.d4f1e5d, the last workspace commit before them, builds and passes its tests.e2ae8f62;826587db(asyncdelete())9e88151c,6226ba82(recommendations),8616e7b5(absorption, limits),e2524741(a mute of 0 ends the mute)768106b3(background mode),48bc6d4e,265952cc(strings),f532d3d2;917e0d27(merge of Loopnext-dev)5adc79cc,7c2999c5(strings),021f6e40(recommendations),73e89c6b(absorption),be083470(end mute),e539d380(Third-Party Smartwatch headers),28928727(shorter texts),16eabfc0(daily limit wording),818c02f8(a failed watch bolus counts for the 5 minutes)60176ac;f84aa68(asyncdelete());3cef80e(README);3b29d0d(Find My Pebble),908c45e(Alarm Sound, shorter texts),dd07b24(Alarm Sound without restarting the watchface),b9935c6(background task while a reading waits)d66e597; Luped Remote0c32603…ba4b825;0bf0a1a,44f6276,44308aa(Luped Remote only with a remote capability declared)115e7b3,d4f1e5d;f6ac0b9(asyncdelete());2210106;ca58869,f7156d1;19d7098(merge ofnext-dev);ece3842,c6edfac7e97b08,ea74742,e3bcb7b,332d9fa,8f74dbe,20ada7f,a2332f8,a9e93df;d70a554pins Loop's strings commitsRelated branches (no PR needed):
1.4.1e296b805maind588b08: Luped 0.1.26, Luped Remote 0.1.8Not in this PR, for reference:
main51e69cffdabear/Triosmartwatch-manager89863359devhosting the same plugins, on this PR's LoopKit (e2524741)experimental/garmin-plugin-from-dev6163db28Dependencies
LupedSmartwatchKitsubmodule (iOS 17.6, like Loop), and through it elbbepTransport 1.4.1, a Swift package with tagged releases. It uses only Apple frameworks (CoreBluetooth, CryptoKit and Combine), has no third-party dependencies, and is MIT licensed, like the plugin.Why a new plugin type
Servicehas no device log, has no channel for the display unit or alerts, uploads incrementally from an anchor, and lives under Services.StatefulPluggablehas no UI, settings row or data delegate.rawState, logs to the device log and belongs under Devices.SmartwatchManagerfollows that model, shaped like Loop's Apple Watch contract (glucose, unit, prediction, loop state) plus host-issued alerts, because watches without iOS notification mirroring can't otherwise know when Loop alerts.LoopKit: the protocols
SmartwatchManager.swiftimports only Foundation and uses no other LoopKit type, so the same plugin can build against other apps' LoopKit forks too. Every method is documented: when it is called, on which thread, and what the other side does.glucose,history,prediction,loopStatus(IOB, COB, last loop, closed loop),therapy(basal, ISF, eventual glucose, sensitivity ratio, correction range, active preset) andalerts. The remote actions commit addsremoteBolus,remoteCarbs,remoteMute,remoteOverrideandremoteTempTarget. The host acts only oneffectiveCapabilities(declared ∩ requested), so a plugin can't get what it didn't declare.SmartwatchRemoteActions.swift):SmartwatchBolusRequest(units, the snapshot's context token, when the user confirmed it,isForced),SmartwatchCarbEntryRequest(grams, when confirmed, and an optionalSmartwatchCarbAbsorption:fast,mediumorslow, which the host maps to its own absorption times),SmartwatchAlertMuteRequest(a duration; 0 ends the host's mute), the preset requests, andSmartwatchBolusRecommendationRequest(grams about to be added, 0 for none, and their absorption), answered with aSmartwatchBolusRecommendation(units, the carbs counted, maximum bolus). Each request refuses with its own error (SmartwatchBolusError, …), which the plugin can pass back to the watch as a reason.SmartwatchStatusSnapshotholds plain mg/dL values. The host fills only what the plugin asks for.SmartwatchAlertcarriesglucoseAlertType(urgentLow,low,high,predictedLow, ornil). Each app names its alerts differently, so the host maps its own, and a watch can colour a low and a high differently without thresholds of its own.SmartwatchManagerUI,SmartwatchManagerUIPluginand the bundle keys, as for CGM and pump plugins.Loop: the host
PluginManagerfinds watch plugins.DeviceDataManagerkeeps one watch manager, saved asSmartwatchManagerStateand kept if a build lacks the plugin. Settings → Devices gets a Watch row: Add Watch, a picker, the plugin's setup and settings.SmartwatchManagerHostsends a snapshot as soon as a reading is stored and again when the loop's output changes, without resending unchanged ones; its display unit is HealthKit's preferred glucose unit (the values themselves are in mg/dL). The Apple Watch path is untouched.AlertForwarderhook onAlertManagerpasses Loop's immediate, unmuted glucose alerts and their retractions to the plugin.recommendManualBoluswith the carbs as the entry they would become, rounded as the bolus check rounds the recommendation it caps at, only while watch boluses are allowed and glucose is recent.Loop/Info.plistlistsbluetooth-peripheralunder#if LOOP_BLUETOOTH_PERIPHERAL, through Info.plist preprocessing set inLoop.xcconfig. ClearingINFOPLIST_PREPROCESSOR_DEFINITIONSleaves it out. The built Info.plist is otherwise unchanged, and the mode does nothing unless a plugin publishes a Bluetooth service.The Pebble plugin
LupedSmartwatchKit: the manager;LupedSmartwatchKitUI: setup and settings;LupedSmartwatchPlugin: the.loopplugin.glucose,history,alerts,loopStatusandtherapy, and requests only what the chosen display needs. For Luped Remote it also declaresremoteMute,remoteCarbs,remoteBolus,remoteOverrideandremoteTempTarget; each stays off until the user allows it in Loop's Watch Permissions.How the link works alongside the Pebble app
Loop doesn't replace the Pebble app; it adds its own channel to the watch, used only by the Luped apps: the watchface, and Luped Remote when it is on (elbbepTransport).
DATA, Find My Pebble and the speaker question from Loop, acknowledged by the watch (rejected while Luped isn't the running watchface), and refresh requests, hourly diagnostics and speaker reports from the watch. elbbepTransport handles sequencing, retransmission and resets.What follows from this:
bluetooth-peripheralbackground mode to keep its service up in the background.Remote actions: the gates
A request from the watch reaches Loop's dosing only if it passes every gate:
remoteBolus, …). Otherwise the host never receives the request. The Pebble plugin declares the five for Luped Remote, which itself is off until turned on: while it is off, no request can arrive.SmartwatchRemoteActionPolicy.swift):inProgress, not a second bolus;remoteActionLimits), for example a longer window for a watch app that can't send a token back. Loop's own checks below still apply.What the watch can and can't do:
Protocol: watch and phone
Messages between the Luped apps and the plugin (PROTOCOL.md); added for Luped Remote, Find My Pebble and the alarm sound:
COMMAND, each ending@<confirmedAt>):mute:<minutes>ormute:cancel(passed to the host as 0, which ends the mute),carbs:<grams>[:<absorption>],override:<preset id>oroverride:cancel,temptarget:<preset id>ortemptarget:cancel(hosts with temp targets), andsignedbolus:<units>[:<grams>[:<absorption>]]@<confirmedAt>#<sid>.<seq>.<mac>.absorptionisfast,mediumorslow; the host maps it to its own absorption times, and older requests without it mean the host's default.v1|<sid>|<seq>|<confirmedAt>|<action>|<units>|<grams>, then|<absorption>only when the request has one, so older requests verify as before.COMMAND recommend:<grams>[:<absorption>](0 g for none) is answered on a new key,RECOMMEND, to Luped Remote only:<grams>:ok:<units>:<maximum bolus>, or<grams>:<outcome>[:<reason>]:refusedwhen boluses from the watch aren't allowed or glucose isn't recent,ignoredfrom a host without recommendations,failedfor an invalid amount.COMMAND presetsis answered on a new key,PRESETS, to Luped Remote only:[overrides, temp targets], each a list of[id, name, running]ornullfor a kind the host doesn't have (Loop: temp targets). Names are cleaned for the watch's fonts (no emoji or control characters, at most 32 characters), an id that isn't 1–64 letters, digits,.,_or-goes as a short~token that the plugin maps back, and at most 16 of each kind are sent.DATAis unchanged.COMMAND findpebble:<seconds>(the plugin sends 60;findpebble:0stops it) to whichever app is running, and to the other one if the watch rejects it. The watch shows a full screen with the light on, buzzes back to back and beeps every 2.5 s where it has a speaker, until the time is up or any button is pressed. It doesn't answer; the message's ACK says it arrived. Older watches ignore it, as they ignore anyCOMMANDthey don't know.COMMAND speaker:on, orspeaker:quietwhile its Quiet Time is on, after its startuprefreshand when Quiet Time changes; watches without one never do. Until a report has arrived, the plugin asks withCOMMAND speaker, once per watchface session after it takes a reading, so a watch added again while its watchface kept running still reports (Luped 0.1.26 and Luped Remote 0.1.8 answer; older ones ignore it). To sound an alarm too, the plugin sends its alert event (DATAindex 9) negative, decided once per alert; older watches only compare that value, so no new index or command. The watch never beeps without buzzing, nor while index 8 isn't above 0.RESULT):<action>:<outcome>[:<reason>], e.g.carbs:refused:too soon; watches read the first two fields. A bolus with carbs is answered twice, carbs first.Questions for reviewers
LoopConfigOverride.xcconfig? This is to be compatible with (or not get in the way of) Tidepool Loop which may or may not want to have this background mode enabled.